﻿using System;
using System.Diagnostics.Contracts;
using AutomaTones.HW_Sense_Compute;
using AutomaTones.Model.Automaton_Model;
using AutomaTones.Model.MIDI;
using Midi;
using Microsoft.Pex.Framework;

namespace AutomaTones.View.Launchpad_View {
    /// <summary>
    /// Launchpad holds all of our functionality needed to visualise our program on the Launchpad.
    /// </summary>
    /// <author>Anders Bech Mellson, anbh@itu.dk</author>
    [PexClass]
    class LaunchPad : IHardwareView {
        private readonly int[,] _gridWithMidiValues = new int[8, 8];
        private Pitch _root = Pitch.CNeg1;
        private int _rapidGridSize=0;

        /// <summary>
        /// Constructer creates a useable Launchpad.
        /// A 2 dimensional int array used to translate from x,y coordinates into Midi NoteOn messages is constructed.
        /// This is needed to be able to say ColorPad(x,y) and then the _gridWithMidiValues grid is used to convert
        /// the x & y back into the Midi Values used to color the pads on the Launchpad.
        /// 
        /// The constructor also opens the Launchpads MidiOut driver.
        /// </summary>
        public LaunchPad() {
            int x = 0;
            int y = 0;
            for (int i = 1; i <= 120; ) {
                if (i % 8 == 0) {
                    _gridWithMidiValues[x, y] = i - 1;
                    i += 9;
                    y++;
                    x = 0;
                }
                else {
                    _gridWithMidiValues[x, y] = i - 1;
                    x++;
                    i++;
                }
            }
            ConnectLaunchpad.Out.Open();
        }

        /// <summary>
        /// Change color of a pad on the Launchpad.
        /// </summary>
        /// <param name="x">The x coordinate on the Launchpad. Valid values are from 1 to 8.</param>
        /// <param name="y">The y coordinate on the Launchpad. Valid values are from 1 to 8.</param>
        /// <param name="color">The PadColor you would like to be lit on the Launchpad.</param>
        private void ColorPad(int x, int y, PadColor color) {
            Contract.Requires(x >= 0 && x <= 7);
            Contract.Requires(y >= 0 && y <= 7);
            if (x < 8 && y < 8) {
                _root = (Pitch)(_gridWithMidiValues[x, y]);
                ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, _root, (int)color);
            }
        }

        /// <summary>
        /// Sets the Launchpad in a default state, and colors the statechange pads.
        /// The 64 pad field is being switched off.
        /// </summary>
        [PexMethod]
        public void ClearGrid() {
            // Special Midi message that the Launchpad translates into a reset message.
            ConnectLaunchpad.Out.SendControlChange(Channel.Channel1, Control.ModulationWheel - 1, 0);
            ColorStateButtons();

            // OK Button
            ColorControlButton(0,PadColor.FullGreen);
            // Clear Button
            ColorControlButton(7, PadColor.FullRed);
        }

        /// <summary>
        /// Colors all the Statechange pad's on the right side of the Launchpad
        /// </summary>
        [PexMethod]
        public void ColorStateButtons() {
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, Pitch.CNeg1 + 24, (int)PadColor.FullRed);
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, Pitch.CNeg1 + 40, (int)PadColor.DimRed);
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, Pitch.CNeg1 + 56, (int)PadColor.FullAmber);
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, Pitch.CNeg1 + 72, (int)PadColor.DimAmber);
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, Pitch.CNeg1 + 88, (int)PadColor.FullGreen);
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel1, Pitch.CNeg1 + 104, (int)PadColor.DimGreen);
        }

        /// <summary>
        /// Set the color of a single pad on the Launchpad.
        /// The method will automaticly convert from a cells status to a color on the Launchpad.
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        /// <param name="state">The state of the cell.</param>
        [PexMethod]
        public void SetCell(int x, int y, State state) {
            Contract.Requires(x >= 0 && x <= 7);
            Contract.Requires(y >= 0 && y <= 7);
            Contract.Requires(state != null);
            ColorPad(x,y,LaunchpadModel.ConvertStateToColor(state));
        }

        /// <summary>
        /// Updates the Launchpad faster than the SetCell method. It does so by setting two LED's at the same time instead of one.
        /// </summary>
        /// <param name="state1"></param>
        /// <param name="state2"></param>
        [PexMethod]
        public void RapidUpdate(State state1, State state2) {
            Contract.Requires(state1!=null && state2 != null);
            // RapidUpdate Midi message, sends two pad updates at a time. Needs a normal Midi message to return to normal functioning again - see below.
            ConnectLaunchpad.Out.SendNoteOn(Channel.Channel3, (Pitch)LaunchpadModel.ConvertStateToColor(state1), (int)LaunchpadModel.ConvertStateToColor(state2));
            
            /* Counter used to check if the entire 64 pad field has been painted.
             * If we didn't check and let the RapidUpdate go above 32, then it would start painting outside the 64 pad field.
             */
            _rapidGridSize++;
            
            // If _rapidGridSize == 32 then we know that the Launchpads 64 pads are updated (2 pads per message times 32).
            if (_rapidGridSize == 32) {
                _rapidGridSize = 0;
                /* Coloring the upperleft button on the Launchpad green.
                 * Only doing this because the Launchpad needs another midimessage than the RapidUpdate
                 * after each runthrough inorder to function normally again.
                 */
                ColorControlButton(0, PadColor.FullGreen);
            }
        }

        /// <summary>
        /// Updates the entire surface of the Launchpad in one go.
        /// </summary>
        /// <param name="stateGrid">Grid to be painted on the Launchpad.</param>
        [PexMethod]
        public void SetGrid(State[,] stateGrid) {
            Contract.Requires(stateGrid!=null);
            Contract.Requires(stateGrid.GetLength(0)<=8 && stateGrid.GetLength(1)<=8);
            for (int x = 0; x < stateGrid.GetLength(0); x++)
                for (int y = 0; y < stateGrid.GetLength(1); y+=2)
                    RapidUpdate(stateGrid[y, x],stateGrid[y+1,x]);
        }

        /// <summary>
        /// Colors the toprow buttons on the Launchpad.
        /// </summary>
        /// <param name="button">The button from left to right you wish to color.</param>
        /// <param name="color">The color you wish the button to get.</param>
        [PexMethod]
        public void ColorControlButton(int button, PadColor color) {
            //Contract.Requires(button >=1 && button <= 8);
            ConnectLaunchpad.Out.SendControlChange(Channel.Channel1, Control.Pan + 94+button, (int)color);
        }

        /// <summary>
        /// Since the Launchpad can only show rectangular grids,
        /// this method is not implemented.
        /// 
        /// It is inherited from IHardwareView made by the brilliant Anders Høst Kjærgaard.
        /// </summary>
        [PexMethod]
        public void SetArbitraryGrid(State[,] stateGrid) {
            Contract.Requires(stateGrid!=null);
            Console.WriteLine("Hardware sets arbitrary grid (Or at least it want's to, but it's so sad - since it cant..).");
        }

        [ContractInvariantMethod]
        private void Invariant() {
            Contract.Invariant(_rapidGridSize >= 0 && _rapidGridSize < 32);
        }
    }
}
